package com.hh.game.service.gateway.webscoket;

import com.hh.game.common.base.manager.session.Session;
import com.hh.game.service.gateway.codec.websocket.MessageDataType;
import com.hh.game.service.gateway.codec.websocket.WebSocketResponse;
import com.hh.game.service.gateway.filter.ip.IpConfig;
import com.hh.game.service.gateway.filter.request.SynRequestConfig;
import com.hh.game.service.gateway.config.TotalConfig;
import com.hh.game.service.gateway.filter.ip.BlackIpFilter;
import com.hh.game.service.gateway.filter.ip.RateLimitConfig;
import com.hh.game.service.gateway.filter.ip.RateLimitFilter;
import com.hh.game.service.gateway.codec.websocket.OutputMessage;
import com.hh.game.service.gateway.filter.request.SynRequestFilter;
import com.hh.game.service.gateway.filter.security.DefaultWebSocketSecurityManager;
import com.hh.game.service.gateway.filter.security.SecurityConfig;
import com.hh.game.service.gateway.filter.security.SecurityFilter;
import com.hh.game.service.gateway.filter.statistics.RedisStatisticsManager;
import com.hh.game.service.gateway.filter.statistics.StatisticsConfig;
import com.hh.game.service.gateway.filter.statistics.StatisticsFilter;
import com.hh.game.service.gateway.manager.RouteEnum;
import com.hh.game.service.gateway.webscoket.config.WebSocketServerConfig;
import com.hh.game.service.gateway.webscoket.handler.*;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.io.File;

public class WebSocketServerInitializer extends ChannelInitializer<NioSocketChannel> {
    private WebSocketServerConfig webSocketServerConfig ;
    private RateLimitConfig rateLimitConfig ;
    private StatisticsFilter statisticsFilter ;
    private SynRequestConfig synRequestConfig ;
    private IpConfig ipConfig ;
    private SecurityConfig securityConfig ;
    private StatisticsConfig statistics;

    public WebSocketServerInitializer(TotalConfig config){
        this.webSocketServerConfig = config.getWebSocketServerConfig();
        this.rateLimitConfig = config.getRateLimitConfig();
        this.statistics = config.getStatisticsConfig();
        this.synRequestConfig = config.getSynRequestConfig();
        this.ipConfig = config.getIpConfig();
        this.securityConfig = config.getSecurityConfig();

        statisticsFilter = new StatisticsFilter(statistics.getTime() ,
                new RedisStatisticsManager(config.getRedisResource() ,statistics.getApplicationName()));
    }


    @Override
    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
        ChannelPipeline pipeline = nioSocketChannel.pipeline();

        /// 黑名单
        pipeline.addFirst(new BlackIpFilter());

        // wss
        if (webSocketServerConfig.isSSL()) {
            SslContext sslContext = SslContextBuilder.forServer(new File(webSocketServerConfig.getSslCertificate()),
                    new File(webSocketServerConfig.getSslCertificateKey()))
                    .build();
            pipeline.addFirst(sslContext.newHandler(nioSocketChannel.alloc()));
        }

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(64 * 1024));
        // 主要用于处理大数据流，比如一个1G大小的文件如果你直接传输肯定会撑暴jvm内存的; 增加之后就不用考虑这个问题了
        pipeline.addLast(new ChunkedWriteHandler());

        // WebSocket数据压缩
        pipeline.addLast(new WebSocketServerCompressionHandler());

        // 路径
        pipeline.addLast(new WebSocketServerProtocolHandler(webSocketServerConfig.getPath()));

        pipeline.addLast(new WebSocketEventChannelHandler());

        //统计
        pipeline.addLast(statisticsFilter);
        // 限流
        pipeline.addLast(new RateLimitFilter(rateLimitConfig.getQps() ,rateLimitConfig.getLimit()));
        // 心跳
        pipeline.addLast(new IdleStateHandler(webSocketServerConfig.getReaderIdleTimeSeconds() ,
                webSocketServerConfig.getWriterIdleTimeSeconds(),webSocketServerConfig.getAllIdleTimeSeconds()));

        // 解码
        pipeline.addLast(new MessageToRequestDecoder());

        // 串行请求检测
        if(synRequestConfig.getRoutes() != null) {
            pipeline.addLast(new SynRequestFilter(synRequestConfig.getRoutes(), ipConfig.getWhite()){
                @Override
                protected void noticeClientSynRequest(ChannelHandlerContext ctx) {
                    OutputMessage msg = new OutputMessage(RouteEnum.SYN_REQUEST_CONCURRENT.route(), MessageDataType.BYTE ,(byte)0);
                    Session session = ctx.channel().attr(Session.SESSION_KEY).get();
                    WebSocketResponse response = new WebSocketResponse(msg , session ,ctx);
                    ctx.channel().writeAndFlush(response);
                }
            });
        }

        // 编码
        pipeline.addLast(new ResponseToMessageEncoder());

        /// 鉴权
        pipeline.addLast(new SecurityFilter(securityConfig, new DefaultWebSocketSecurityManager()) {
            @Override
            protected void noticeClientAccessDenied(ChannelHandlerContext ctx) {
                OutputMessage msg = new OutputMessage(RouteEnum.ACCESS_DENIED.route(), MessageDataType.BYTE ,(byte)0);
                Session session = ctx.channel().attr(Session.SESSION_KEY).get();
                WebSocketResponse response = new WebSocketResponse(msg , session ,ctx);
                ctx.channel().writeAndFlush(response);
            }
        });

        // 业务处理器
        pipeline.addLast(new GatewayHandler(this.webSocketServerConfig));
    }

}
